using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using WPCoco.Graphics;
using System.Diagnostics;

namespace WPCoco
{
    /// <summary>
    /// Class that show an image from a portion of one texture,
    /// also allow batch drawing is are linked to an AtlasSprite use this a very 
    /// important performance tip
    /// </summary>
    public class Sprite : AffineNode, ITextured, IColored, IAtlasSpriteClient, IGraphicState
    {
        /// <summary>
        /// Create an empty sprite
        /// </summary>
        public Sprite()
        {
            Anchor = new Vector2(0.5f, 0.5f);
        }

        /// <summary>
        /// Create a sprite with the all the parameters
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="textureRegion">Region from the texture to draw</param>
        /// <param name="textureTrim">Trim for that region</param>
        /// <param name="color">Color of sprite</param>
        public Sprite(Texture2D texture, Rectangle textureRegion, Trim textureTrim, Color color)
            : this()
        {
            Texture = texture; TextureRegion = textureRegion; TextureTrim = textureTrim;
            Color = color; 
            Anchor = new Vector2(0.5f, 0.5f);
        }

        /// <summary>
        /// Create a sprite without triming
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="textureRegion">Region from the texture to draw</param>
        /// <param name="color">Color of sprite</param>
        public Sprite(Texture2D texture, Rectangle textureRegion, Color color)
            : this(texture, textureRegion, Trim.Empty, color)
        {

        }

        /// <summary>
        /// Create a sprite using the whole texture and a color
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="color">Color of the sprite</param>
        public Sprite(Texture2D texture, Color color)
            : this(texture, new Rectangle(0, 0, texture.Width, texture.Height), color)
        {

        }

        /// <summary>
        /// Create a sprite using the whole texture and a white color
        /// </summary>
        /// <param name="texture">Texture to use</param>
        public Sprite(Texture2D texture)
            : this(texture, new Rectangle(0, 0, texture.Width, texture.Height), Color.White)
        {

        }

        /// <summary>
        /// Create an sprite using a specified region of a texture with white color
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="textureRegion">from the texture to draw</param>
        public Sprite(Texture2D texture, Rectangle textureRegion)
            : this(texture, textureRegion, Color.White)
        {
        }

        /// <summary>
        /// Create a sprite using a texture frame
        /// </summary>
        /// <param name="frame">TextureFrame data to use</param>
        public Sprite(TextureFrame frame)
            : this(frame.Texture, frame.TextureRegion, frame.TextureTrim, Color.White)
        {

        }

        /// <summary>
        /// Set all the sprite data from a TextureFrame
        /// </summary>
        /// <param name="frame">Texture frame to use</param>
        public void SetTextureFrame(TextureFrame frame) 
        {
            Texture = frame.Texture;
            TextureRegion = frame.TextureRegion;
            TextureTrim = frame.TextureTrim;
        }

        protected override void BeginDraw(GameTime gameTime)
        {
            base.BeginDraw(gameTime);
            if (this.IsAtlasOwner())
            {
                Atlas.BaseWorldMatrix = WorldMatrix.Shared.CurrentMatrix;
                Atlas.WorldMatrix.Begin();
            }
            else
            if (this.IsAtlasUsed())
            {
                Atlas.WorldMatrix.Push(WorldViewMatrix);
            }
        }

        public override void SelfDraw(GameTime gameTime)
        {
            if (!this.IsAtlasUsed())
            {
                if (texture == null) return;
                CachedSpriteBatch spriteBatch = SpriteBatchCache.GetVolatile();
                spriteBatch.Begin(SpriteSortMode.Deferred, blend, sampler, depthStencil, rasterizer, null, WorldMatrix.Shared.CurrentMatrix);
                spriteBatch.Draw(texture, new Vector2(textureTrim.Left, textureTrim.Top), textureRegion, Color);
                spriteBatch.End();
            }
            else
            {
                if (this.IsAtlasLinked())
                    UpdateQuad();
            }
        }

        protected override void EndDraw(GameTime gameTime)
        {
            base.EndDraw(gameTime);
            if (this.IsAtlasOwner())
            {
                Atlas.WorldMatrix.End();
                Atlas.Draw();
            }
            else 
            if (this.IsAtlasUsed()) 
            {
                Atlas.WorldMatrix.Pop();
            }
        }

        void UpdateSize() 
        {
            Width = textureRegion.Width + textureTrim.TotalWidth;
            Height = textureRegion.Height + textureTrim.TotalHeight;
        }

        protected override void IsDirtySet()
        {
            if (!this.IsAtlasOwner())
                ReportChange(QuadDirtyComponent.Position);
        }

        protected override void Linked()
        {
            IAtlasSpriteClient client = Owner as IAtlasSpriteClient;
            if (client != null) 
            {
                ParentClient = client;
                Atlas = client.Atlas;
            }
            if (Visible && this.CanRegister())
                Atlas.Register(this);
        }

        protected override void Unlinked()
        {
            base.Unlinked();
            if (this.IsAtlasLinked()) 
            {
                Atlas.Unregister(this);
                Atlas = null;
            }
        }

        protected override void VisibilityChange()
        {
            if (Visible)
            {
                if (this.CanRegister())
                    Atlas.Register(this);
            }
            else
            {
                if (this.IsAtlasLinked())
                    Atlas.Unregister(this);
            }
        }

        #region ITextured Members

        Rectangle textureRegion;

        public Rectangle TextureRegion
        {
            get { return textureRegion; }
            set 
            { 
                textureRegion = value;
                UpdateSize();
                quadDirty |= QuadDirtyComponent.Texture;
            }
        }

        Texture2D texture;

        public Texture2D Texture
        {
            get { return texture; }
            set 
            { 
                texture = value;
                quadDirty |= QuadDirtyComponent.Texture;
            }
        }

        Trim textureTrim = Trim.Empty;

        public Trim TextureTrim
        {
            get { return textureTrim; }
            set 
            { 
                textureTrim = value;
                UpdateSize();
                quadDirty |= QuadDirtyComponent.Texture;
            }
        }

        #endregion

        #region IColored Members

        Color color = Color.White;

        public virtual Color Color
        {
            get { return color; }
            set
            { 
                color = value;
                quadDirty |= QuadDirtyComponent.Color;
            }
        }

        #endregion

        #region IAtlasSpriteClient Members

        AtlasSprite atlas = null;

        public AtlasSprite Atlas
        {
            get { return atlas; }
            set { atlas = value; }
        }

        int atlasIndex = -1;

        public int AtlasIndex
        {
            get { return atlasIndex; }
            set { atlasIndex = value; }
        }

        IAtlasSpriteClient parentClient;

        public IAtlasSpriteClient ParentClient
        {
            get { return parentClient; }
            set 
            {
                parentClient = value;
            }
        }

        public IAtlasSpriteClient GetNextNodeLinked(IAtlasSpriteClient client) 
        {
            int index = FindNode((Node)client);
            if (index == -1) return null;
            IAtlasSpriteClient c = null, r = null;
            for (int i = index + 1; i < ChildCount; i++)
            {
                c = this[i] as IAtlasSpriteClient;
                if (c.IsAtlasLinked())
                {
                    r = c;
                    break;
                }
            }
            if (r == null) 
            {
                if (this[index].ZOrder < 0) r = this;
            }
            return r;
        }

        QuadDirtyComponent quadDirty = QuadDirtyComponent.All;

        Vector2[] posArray = new Vector2[4];

        void UpdateQuad() 
        {
            if (quadDirty == QuadDirtyComponent.None) return;

            // Color Components
            if (((int)quadDirty & (int)QuadDirtyComponent.Color) == (int)QuadDirtyComponent.Color) 
            {
                quad.vertexs[0].Color = quad.vertexs[1].Color = quad.vertexs[2].Color = quad.vertexs[3].Color = color;
            }

            // Position Components
            if (((int)quadDirty & (int)QuadDirtyComponent.Position) == (int)QuadDirtyComponent.Position)
            {
                posArray[0] = new Vector2(textureTrim.Left, textureTrim.Top);
                posArray[1] = new Vector2(textureTrim.Left + textureRegion.Width, textureTrim.Top);
                posArray[2] = new Vector2(textureTrim.Left + textureRegion.Width, textureTrim.Top + textureRegion.Height);
                posArray[3] = new Vector2(textureTrim.Left, textureTrim.Top + textureRegion.Height);
                Matrix m = Atlas.WorldMatrix.CurrentMatrix;
                Vector2.Transform(posArray, ref m, posArray);
                quad.vertexs[0].Position = new Vector3(posArray[0], 0);
                quad.vertexs[1].Position = new Vector3(posArray[1], 0);
                quad.vertexs[2].Position = new Vector3(posArray[2], 0);
                quad.vertexs[3].Position = new Vector3(posArray[3], 0);
            }

            // Texture Components
            if (((int)quadDirty & (int)QuadDirtyComponent.Texture) == (int)QuadDirtyComponent.Texture)
            {
                RectangleF normalized = TextureFrame.NormalizeTextureRegion(textureRegion, texture);
                quad.vertexs[0].TextureCoordinate = new Vector2(normalized.X, normalized.Y);
                quad.vertexs[1].TextureCoordinate = new Vector2(normalized.X + normalized.Width, normalized.Y);
                quad.vertexs[2].TextureCoordinate = new Vector2(normalized.X + normalized.Width, normalized.Y + normalized.Height);
                quad.vertexs[3].TextureCoordinate = new Vector2(normalized.X, normalized.Y + normalized.Height);
            }

            quadDirty = QuadDirtyComponent.None;
            Atlas.Update(this);
        }

        AtlasQuad quad = new AtlasQuad();
       
        public AtlasQuad GetQuad()
        {
            return quad;
        }

        public void ReportChange(QuadDirtyComponent component)
        {
            quadDirty |= component;
            if (!this.IsAtlasLinked()) return;
            if ((component & QuadDirtyComponent.Position) == QuadDirtyComponent.Position)
            {
                foreach (var node in this.GetContainerEnumerator())
                    node.ReportChange(component);
            }
        }

        public void RequestRegister(AtlasSprite atlas, IAtlasSpriteClient parent)
        {
            if (this.IsAtlasOwner() && atlas != Atlas) return;
            ParentClient = parent;
            Atlas = atlas;
            if (Visible && this.CanRegister())
                atlas.Register(this);
        }

        #endregion

        #region IContainer<IAtlasSpriteClient> Members

        public new IEnumerable<IAtlasSpriteClient> GetContainerEnumerator()
        {
            IAtlasSpriteClient client;
            for (int i = 0; i < ChildCount; i++)
            {
                client = base[i] as IAtlasSpriteClient;
                if (client != null)
                    yield return client;
            }
            yield break;
        }

        #endregion

        #region IReverseContainer<IAtlasSpriteClient> Members

        public IEnumerable<IAtlasSpriteClient> GetReverseContainerEnumerator()
        {
            IAtlasSpriteClient client;
            for (int i = ChildCount - 1; i >= 0; i--)
            {
                client = base[i] as IAtlasSpriteClient;
                if (client != null)
                    yield return client;
            }
            yield break;            
        }

        #endregion

        #region IGraphicState Members

        SamplerState sampler;

        public SamplerState Sampler
        {
            get { return sampler; }
            set { sampler = value; }
        }

        DepthStencilState depthStencil;

        public DepthStencilState DepthStencil
        {
            get { return depthStencil; }
            set { depthStencil = value; }
        }

        BlendState blend;

        public BlendState Blend
        {
            get { return blend; }
            set { blend = value; }
        }

        RasterizerState rasterizer;

        public RasterizerState Rasterizer
        {
            get { return rasterizer; }
            set { rasterizer = value; }
        }

        #endregion
    }
}
